####———————— 第九章 类
# 面向对象编程（object-oriented programming，OOP）是最有效的软件编写方法之一
# 在面向对象编程中，你编写表示现实世界中的事物和情景的类（class）
# 并基于这些类来创建对象（object）
# 在编写类时，你要定义一批对象都具备的通用行为
# 在基于类创建对象时，每个对象都自动具备这种通用行为。
# 然后，你可根据需要赋予每个对象独特的个性。

# 根据类类创建对象称为 实例化，这让你能够实用类的实例（instance）
# 在本章中，你将编写一些类并创建其实例。你将指定可在实例中存储什么信息，定义可对这些实例执行哪些操作
# 你还将编写一些类来扩展既有类的功能，让相似的类能够共享功能，从而使用更少的代码做更多的事情
# 你将把自己编写的类存储在模块中，并在自己的程序文件中导入其他程序员编写的类

# 学习面向对象编程有助于你像程序员那样看世界，并且真正明白自己编写的代码
# 不仅是各行代码的作用，还有代码背后更宏大的概念
# 了解类背后的概念可培养逻辑思维能力，让你能够通过编写程序来解决遇到的几乎任何问题

# 9、1 创建和使用类
# 使用类几乎可以模拟任何东西。下面来别写一个表示小狗的简单类 Dog —— 他表示的不是特定的小狗，而是任何小狗
# 对于大多数宠物狗，我们知道其都有名字和年龄，还知道会坐下和打滚。
# 由于小狗具备上述的两项信息（名字和年龄）和两种行为（坐下和打滚）
# 我们的Dog类将包含他们，这个类让Python知道如何创建表示小狗的对象
# 编写这个类后，我们将使用它来创建表示特定小狗的实例

# 9.1.1 创建Dog类
# 根据Dog类创建的每个实例都将存储名字和年龄，而且我们会赋予每条小狗坐下（ sit() ）和打滚（ roll_over() ）的能力：

class Dog:
    """一次模拟小狗的简单尝试"""
    def __init__(self, name, age):
        """初始化属性 name 和 age"""
        self.name = name
        self.age = age

    def sit(self):
        """模拟小狗收到命令时坐下"""
        print(f"{self.name} is now sitting.")

    def roll_over(self):
        """模拟小狗收到命令时打滚"""
        print(f"{self.name} rolled over!")

# 首先定义一个名为 Dog 的类。
# 根据约定，在Python中，首字母大写的名称指的是类
# 这是因为我们创建的全新的类，所以定义时不加括号。然后是一个文档字符串，对这个类的功能做了描述

# _init_() 方法
# 类中的函数称为方法。 你在前面学到的有关函数的一切都适用于方法，就目前而言，唯一重要的差别是调用方法的方式
# _init_() 是一个特殊的方法，每当你根据Dog类创建新实例时，Python都会自动运行它。
# 在这个方法的名称中，开头和末尾各有两个下划线，这是一种约定，避免Pyhton默认方法与普通方法名称发生冲突
# 务必确保 _init_() 的两边都有下划线

# 我们将 _init_() 方法定义成包含三个形参： self、name和age
# 在这个方法的定义中，形参self必不可少，而且必须位于其他形参的前面。
# 为何必须在方法定义中包含形参 self 呢？
# 因为当Python调用这个方法来创建 Dog 实例时，将自动传入实参 self
# 每个与实例相关联的方法调用都会自动传递实参 self，该实参是一个执行实例本身的引用，让实例能够访问类中的属性和方法。
# 当我们创建Dog实例时，Python将调用Dog类的 _init_() 方法
# 我们将通过实参向 Dog() 传递名字和年龄；self则会自动传递，因此不需要我们来传递。
# 每当我们根据 Dog 类创建实例时，都只需给最后两个形参 （name和age）提供值

# Dog类还定义了另外两个方法： sit() 和 roll_over()
# 由于这些方法执行时不需要额外的信息，因此只有一个形参self

# 9.1.2 根据类创建实例

# 可以将类视为有关如何创建实例的说明。例如，Dog类就是一系列说明，让Python知道如何创建表示特定小狗的实例

my_dog = Dog('willie', 6)

print(f"My dog's name is {my_dog.name}")
print(f"My dog is {my_dog.age} years old.")

# 我们让Python 创建一条名为 'willie'、年龄为 6 的小狗
# 在处理这行代码时，Python调用Dog类的 _init_() 方法，并传入实参 ’Willie‘和 6
# _init_() 方法创建一个表示特定小狗的实例，并且使用提供的值设置属性 name 和 age。
# 接下来， Python 返回一个表示这条小狗的实例。

# 01 访问属性
# 要访问实例的属性，可使用点号
# my_dog.name

# 02 调用方法
# 根据Dog类创建实例后，就能使用点号来调用 Dog类中定义的任何方法了

my_dog = Dog('willie', 6)
my_dog.sit()
my_dog.roll_over()

# 这种语法很有用。如果给属性和方法指定了合适的描述性名称，如name、age、sit()和 roll_over()
# 即便对于从未见过的代码块，我们也能够轻松地推断出他是做什么的

# 03 创建多个实例

my_dog = Dog('willie', 6)
your_dog = Dog('Lucy', 3)

print(f"My dog's name is {my_dog.name}")
print(f"My dog is {my_dog.age} yeas old.")
my_dog.sit()

print(f"Your dog's name is {your_dog.name}")
print(f"Your dog is {your_dog.age} yeas old.")
your_dog.sit()

# 9、2 使用类和实例
# 可以使用类来模拟现实世界中的很多情景
# 类编写好之后，大部分时间花在根据类创建实例上

# 9.2.1 Car 类

# 编写一个表示汽车的类，他存储了有关汽车的信息，并提供了一个汇总这些信息的方法

# class Car:
#     """一次模拟汽车的简单尝试"""
#
#     def __init__(self, make, model, year):
#         """初始化描述汽车的属性"""
#         self.make = make
#         self.model = model
#         self.year = year
#
#     def get_descriptive_name(self):
#         """返回格式规范的描述性信息"""
#         long_name = f"{self.year} {self.make} {self.model}"
#         return long_name.title()
#
# my_new_car = Car('audi', 'a4', 2024)
# print(my_new_car.get_descriptive_name())

# 为了让这个类更有趣，下面给他添加一个随时间变化的属性，用于存储汽车的行驶里程

# 9.2.2 给属性指定默认值
# 有些属性无需通过形参来定义，可以在 _init_() 方法中为其指定默认值

# 下面添加一个 odometer_reading 的属性，其初始值总是 0
# 在添加一个 read_odometer() 方法，用于读取汽车的里程表

# class Car:
#     """一次简单的。。。"""
#
#     def __init__(self, make, model, year):
#         """初始化描述汽车的属性"""
#         self.make = make
#         self.model = model
#         self.year = year
#         self.odometer_reading = 0
#
#     def get_descriptive_name(self):
#         """返回格式规范的描述性信息"""
#         long_name = f"{self.year} {self.make} {self.model}"
#         return long_name.title()
#
#     def read_odometer(self):
#         """打印一条指出汽车行驶里程的消息"""
#         print(f"This car has {self.odometer_reading} miles on it.")
#
# my_new_car = Car('audi', 'A4', 2024)
# print(my_new_car.get_descriptive_name())
# my_new_car.read_odometer()

# 9.2.3 修改属性的值

# 可以用三种方法修改属性的值：直接通过实例修改、通过方法设置、以及通过方法递增（增加特定的值）

# 01 直接修改属性的值
# 最简单的方法是通过实例直接访问它，然后修改

# my_new_car.odometer_reading = 23
# my_new_car.read_odometer()

# 02 通过方法修改属性的值
# 有一个替你更新属性的方法大有裨益。这样就无需直接访问属性了
# 而是将值传递给方法，由它在内部进行更新
# 在 Car 类内部定义一个更新属性值的 方法（函数）


# class Car:
#     """一次简单的。。。"""
#
#     def __init__(self, make, model, year):
#         """初始化描述汽车的属性"""
#         self.make = make
#         self.model = model
#         self.year = year
#         self.odometer_reading = 0
#
#     def get_descriptive_name(self):
#         """返回格式规范的描述性信息"""
#         long_name = f"{self.year} {self.make} {self.model}"
#         return long_name.title()
#
#     def read_odometer(self):
#         """打印一条指出汽车行驶里程的消息"""
#         print(f"This car has {self.odometer_reading} miles on it.")
#
#     def update_odometer(self, mileage):
#         """将里程表读数设定为指定的值"""
#         # self.odometer_reading = mileage
#         # 还可以对此方法进行扩展，比如，禁止里程表往回调（因为里程只会越来越长）
#         if mileage >= self.odometer_reading:
#             self.odometer_reading = mileage
#         else:
#             print("You can't roll back an odometer! ")
#
# my_new_car = Car('audi', 'A4', 2024)
# my_new_car.update_odometer(23)
# my_new_car.read_odometer()
#
# my_new_car.update_odometer(12)
# my_new_car.read_odometer()


# 03 通过方法让属性的值递增

class Car:
    """一次简单的。。。"""

    def __init__(self, make, model, year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        """返回格式规范的描述性信息"""
        long_name = f"{self.year} {self.make} {self.model}"
        return long_name.title()

    def read_odometer(self):
        """打印一条指出汽车行驶里程的消息"""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
        """将里程表读数设定为指定的值"""
        # self.odometer_reading = mileage
        # 还可以对此方法进行扩展，比如，禁止里程表往回调（因为里程只会越来越长）
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer! ")

    def increment_odometer(self, miles):
        """将里程表读数增加指定的量"""
        # self.odometer_reading += miles
        # 也可以扩展，禁止增加值为负值
        if miles >= 0:
            self.odometer_reading += miles
        else:
            print("You can't enter a negative value!")


my_used_car = Car('subaru', 'outback', 2019)

my_used_car.update_odometer(23_500)  # 23500 的表示方法
my_used_car.read_odometer()

my_used_car.increment_odometer(500)
my_used_car.read_odometer()

my_used_car.increment_odometer(-500)
my_used_car.read_odometer()































































